package jf.states;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jf.states.graph.ExpirationDescription;
import jf.states.graph.GraphDescription;
import static jf.collections.CollectionF.asList;

/**
 * @author ivanalx
 * @date 06.05.2009 17:27:10
 */
public class ExpirationStateProcessor<S, A, T> extends StateProcessor<S, A, T>{
	private final ExpirationDescription<S, A, T> expirationDescription;

	private final Map<T, Long> willExpireInTime = new ConcurrentHashMap<T, Long>();

	public ExpirationStateProcessor(
			GraphDescription<S, A, T> graphDescription,
			ExpirationDescription<S, A, T> expirationDescription
	)
	{
		super(graphDescription);
		this.expirationDescription = expirationDescription;
	}

	/**
	 * Process action on object.
	 * @param o object for action process
	 * @param action action for process
	 * @return new state of object
	 * @throws NoValidEdgeForStateAndAction given action for this state is not exists
	 * @throws NoSuchObjectInProcessor there is no object in processor
	 */
	@Override
	public S processAction(T o, A action) {
		S s = super.processAction(o, action);
		Long timeForExpire = expirationDescription.getTimeForExpireForObject(s, o);
		if (timeForExpire != null) {
			willExpireInTime.put(o, System.currentTimeMillis() + timeForExpire);
		} else {
			willExpireInTime.remove(o);
		}
		return s;
	}

	/**
	 * Remove object from processor
	 * @param object object for remove
	 * @return current state for removing object
	 */
	@Override
	public S removeFromGraph(T object) {
		willExpireInTime.remove(object);
		return super.removeFromGraph(object);
	}

	/**
	 * Add object to graph with inital state.
	 * @param object object for adding
	 * @param initalStage inital stage for adding
	 */
	@Override
	public void putToGraph(T object, S initalStage) {
		super.putToGraph(object, initalStage);
		Long timeForExpire = expirationDescription.getTimeForExpireForObject(initalStage, object);
		if (timeForExpire != null) {
			willExpireInTime.put(object, System.currentTimeMillis() + timeForExpire);
		} else {
			willExpireInTime.remove(object);
		}
	}

	/**
	 * Return all object in check list for expiration
	 * @return
	 */
	public List<T> objectForExpirationCheck() {
		return asList(willExpireInTime.keySet());
	}

	/**
	 * Check for object experation. If object is expired, expiration action will be executed in this thread.
	 * @param o object for check
	 * @return current state of object
	 * @throws NoSuchObjectInProcessor for not exists object.
	 * @throws NoValidEdgeForStateAndAction given action for this state is not exists
	 */
	public S checkForExpireAndExecuteExpirationAction(T o) {
		final TimeHolder<S> holder = (TimeHolder<S>) getHolderForObject(o);
		if (holder == null) {
			throw new NoSuchObjectInProcessor();
		}
		synchronized (holder) {
			Long willExpAt = willExpireInTime.get(o);
			if (willExpAt != null) {
				if (System.currentTimeMillis() > willExpAt) {
					A action = expirationDescription.getActionForExpiredObject(holder.getState());
					return processAction(o, action);
				} else {
					return holder.getState();
				}
			} else {
				return null;
			}
		}
	}


	@Override
	protected StateHolder<S> createHolder(S initalStage) {
		return TimeHolder.create(initalStage);
	}

	private static class TimeHolder<S> extends StateHolder<S> {
		private long expireInTime;

		public static <S> TimeHolder<S> create(S state) {
			return new TimeHolder<S>(state);
		}

		private TimeHolder(S state) {
			this.setState(state);
			expireInTime = Long.MAX_VALUE;
		}

		public long getExpireInTime() {
			return expireInTime;
		}

		public void setExpireInTime(long expireInTime) {
			this.expireInTime = expireInTime;
		}
	}
}
